package com.ym.core.exception.handler;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.thread.GlobalThreadPool;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentParser;
import com.alibaba.fastjson.JSON;
import com.ym.common.constant.Coder;
import com.ym.common.constant.UserDetails;
import com.ym.common.utils.IpUtil;
import com.ym.common.utils.ServletUtil;
import com.ym.core.domain.APIResponse;
import com.ym.core.exception.BusinessException;
import com.ym.core.exception.CodeErrorException;
import com.ym.core.exception.SysLoginException;
import com.ym.core.exception.SysLoginHeaderException;
import com.ym.core.shiro.ShiroUtil;
import com.ym.medical.dao.SysErrorLogDao;
import com.ym.medical.domain.entity.SysErrorLogEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ConcurrentAccessException;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.pam.UnsupportedTokenException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.hibernate.validator.internal.engine.ConstraintViolationImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Map;
import java.util.Set;

/**
 * 统一异常处理
 */
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

	@Autowired
	private SysErrorLogDao sysErrorLogDao;

	@ResponseBody
	@ResponseStatus(value = HttpStatus.FORBIDDEN)
	@ExceptionHandler(value = UnauthenticatedException.class)
	public APIResponse handleException(UnauthenticatedException e) {
		log.error("find exception:e={}", e);
		return APIResponse.fail(Coder.Login.LOGIN_EXPIRED);
	}

	@ResponseBody
	@ResponseStatus(value = HttpStatus.FORBIDDEN)
	@ExceptionHandler(value = UnauthorizedException.class)
	public APIResponse handleException(UnauthorizedException e) {
		log.error("未授权异常 error:e={}", e);
		return APIResponse.fail(Coder.Login.NO_PERMISSION);
	}

	@ResponseBody
	@ExceptionHandler(value = AuthenticationException.class)
	public APIResponse handleException(AuthenticationException e) {
		log.error("登录错误 error:e={}", e);
		Coder resultCode = Coder.System.FAIL;
		if (e instanceof UnknownAccountException) {
			// 未知账号
			resultCode = Coder.Login.ACCOUNT_DOES_NOT_EXIST;
		} else if (e instanceof IncorrectCredentialsException) {
			// 未知凭证
			resultCode = Coder.Login.ACCOUNT_PASSWORD_IS_WRONG;
		} else if (e instanceof ExpiredCredentialsException) {
			// 凭证过期
			resultCode = Coder.Login.NO_LOGIN;
		} else if (e instanceof ConcurrentAccessException) {
			// 并发访问异常
			resultCode = Coder.Login.USER_LOGGING_IN_MULTIPLE_PLACES;
		} else if (e instanceof ExcessiveAttemptsException) {
			// 并发访问异常
			resultCode = Coder.Login.THE_NUMBER_OF_AUTHENTICATION_EXCEEDS_THE_LIMIT;
		} else if (e instanceof LockedAccountException) {
			// 账户被锁定
			resultCode = Coder.Login.ACCOUNT_IS_LOCKED;
		} else if (e instanceof DisabledAccountException) {
			// 禁用账号
			resultCode = Coder.Login.ACCOUNT_HAS_BEEN_DISABLED;
		} else if (e instanceof UnsupportedTokenException) {
			//使用了不支持的Token
			resultCode = Coder.Login.TOKEN_ERROR;
		} else if (e instanceof CodeErrorException) {
			//验证码错误
			resultCode = Coder.Login.VERIFICATION_CODE_ERROR;
		} else if (e instanceof SysLoginException) {
			//远程调用异常
			resultCode = Coder.Login.LOGIN_FAILED;
		} else if (e instanceof SysLoginHeaderException) {
			//跨服务登录restTemplate组装异常
			resultCode = Coder.Login.HEADER_FAILED;
		}
		return APIResponse.fail(resultCode);
	}

	@ExceptionHandler(value = BusinessException.class)
	@ResponseBody
	public APIResponse handleException(BusinessException e) {
		log.error("业务异常 Error:e={}", e);
		return APIResponse.fail(e.getMessage());
	}

	/**
	 * 自定义验证异常
	 */
	@ExceptionHandler(BindException.class)
	@ResponseBody
	public APIResponse validatedBindException(BindException e) {
		log.error("参数校验异常 Error:e={}", e);
		String message = e.getAllErrors().get(0).getDefaultMessage();
		return APIResponse.fail(Coder.System.BAD_REQUEST.build(message));
	}

	/**
	 * 自定义验证异常
	 */
	@ExceptionHandler(MethodArgumentNotValidException.class)
	@ResponseBody
	public APIResponse validExceptionHandler(MethodArgumentNotValidException e) {
		log.error("参数校验异常 Error:e={}", e);
		String message = e.getBindingResult().getFieldError().getDefaultMessage();
		return APIResponse.fail(message);
	}

	@ExceptionHandler(value = ConstraintViolationException.class)
	@ResponseBody
	public APIResponse handleException(ConstraintViolationException e) {
		Set<ConstraintViolation<?>> constraintViolations = e.getConstraintViolations();
		StringBuilder builder = new StringBuilder();
		constraintViolations.forEach(n -> {
			ConstraintViolationImpl info = (ConstraintViolationImpl) n;
			String propertyPath = info.getPropertyPath().toString();
			builder.append(propertyPath.substring(propertyPath.lastIndexOf(".") + 1, propertyPath.length()) + info.getMessage() + ",");
		});
		String msg = builder.toString().substring(0, builder.length() - 1);
		log.error("参数校验异常 Error:e={}", msg);
		return APIResponse.fail(msg);
	}


	@ExceptionHandler(value = HttpMediaTypeNotSupportedException.class)
	@ResponseBody
	public APIResponse handleException(HttpMediaTypeNotSupportedException e) {
		log.error("媒体类型错误 Error:e={}", e);
		return APIResponse.fail(Coder.System.UNSUPPORTED_TYPE.build(e.getContentType().toString()));
	}

	@ExceptionHandler(value = MissingServletRequestParameterException.class)
	@ResponseBody
	public APIResponse handleException(MissingServletRequestParameterException e) {
		log.error("请求参数不完整 Error:e={}", e);
		return APIResponse.fail(Coder.System.BAD_REQUEST.build(e.getMessage()));
	}


	@ExceptionHandler(value = Exception.class)
	@ResponseBody
	public APIResponse handleException(Exception e) {
		log.error("未知异常 Error:e={}", e);
		recordError(e);
		return APIResponse.fail(Coder.System.SYS_UNKNOW_ERROR);
	}

	/**
	 * 记录错误日志
	 *
	 * @param e
	 * @return
	 */
	private void recordError(Throwable e) {
		final UserAgent userAgent = UserAgentParser.parse(ServletUtil.getRequest().getHeader("User-Agent"));
		final String ip = IpUtil.getIpAddr(ServletUtil.getRequest());
		final UserDetails loginUser = ShiroUtil.getLoginUser(true);
		final HttpServletRequest request = ServletUtil.getRequest();
		final String requestURI = request.getRequestURI();
		final String method = request.getMethod();
		String requestParams = null;
		if ("POST".equals(method) || "PUT".equals(method)) {
			requestParams = ServletUtil.getBody(request);
		} else {
			Map<String, String> paramMap = ServletUtil.getParamMap(request);
			if (paramMap != null) {
				requestParams = JSON.toJSONString(paramMap);
			}
		}
		final String finalRequestParams = requestParams;
		String address = IpUtil.getAddress(ip);
		final String finalAddress = address;
		GlobalThreadPool.execute(() -> {
			final SysErrorLogEntity sysErrorLogEntity = new SysErrorLogEntity();
			sysErrorLogEntity.setUserAgent(userAgent.getOs() + " " + userAgent.getBrowser());
			sysErrorLogEntity.setIpAddress(finalAddress);
			sysErrorLogEntity.setIp(ip);
			sysErrorLogEntity.setRequestMethod(method);
			sysErrorLogEntity.setRequestParams(finalRequestParams);
			sysErrorLogEntity.setRequestUri(requestURI);
			sysErrorLogEntity.setErrorSimpleInfo(ExceptionUtil.getRootCauseMessage(e));
			sysErrorLogEntity.setErrorInfo(ExceptionUtil.stacktraceToString(e));
			if (loginUser != null) {
				sysErrorLogEntity.setCreator(loginUser.getId());
				sysErrorLogEntity.setCreatorName(loginUser.getUsername());
			}
			sysErrorLogDao.insertSelective(sysErrorLogEntity);
		});
	}

}
